using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Security;
using OpenWaves.ImageTransformations;
using OpenWaves.Web.ImageTransformations;

namespace OpenWaves.Web.Controls
{
    public class ResponsiveWebImageTransformationModuleImplementation : IResponsiveWebImageTransformationService, IWebImageTransformationModuleImplementation
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
        static ResponsiveWebImageTransformationModuleImplementation()
        {
            //
            // HACK: This dummy call is needed because MachineKey.Decode throws NullReferenceException
            // when called before MachineKey.Encode was called.
            // 
            MachineKey.Encode(new byte[0], MachineKeyProtection.Validation);
        }

        private readonly IWebImageTransformationService webImageTransformationService;

        public ResponsiveWebImageTransformationModuleImplementation() : this(ServiceLocator.Resolve<IWebImageTransformationService>())
        {
        }

        public ResponsiveWebImageTransformationModuleImplementation(IWebImageTransformationService webImageTransformationService)
        {
            Contract.Requires<ArgumentNullException>(webImageTransformationService != null);

            this.webImageTransformationService = webImageTransformationService;
        }

        public Url GetTransformedImageUrl(Url imageUrl, IEnumerable<KeyValuePair<string, IImageTransformation>> breakpoints)
        {
            var a = breakpoints.ToDictionary(p => p.Key, p => p.Value.Serialize());
            var b = UrlQueryCollection.FromNameValueDictionary(a);
            var c = MachineKey.Encode(Encoding.ASCII.GetBytes(b.ToString()), MachineKeyProtection.Validation);

            return imageUrl.GetBuilder().AddParameter("r", c).ToUrl();
        }

        public bool PostAuthorizeRequest(HttpContextBase context)
        {
            var url = context.Request.GetRawRelativeUrl();

            string encodedBreakpoints;
            if (url.Query.TryGetValue("r", out encodedBreakpoints) == false)
                return false;

            if (String.IsNullOrEmpty(encodedBreakpoints))
                return false;

            var breakpointsString = DecodeBreakpoints(encodedBreakpoints);

            string breakpointName;
            if (url.Query.TryGetValue("b", out breakpointName) == false)
                return false;

            if (string.IsNullOrEmpty(breakpointName))
                return false;

            string transformationString;
            if (UrlQueryCollection.Parse(breakpointsString).TryGetValue(breakpointName, out transformationString) == false)
                return false;

            IImageTransformation transformation;
            if (ImageTransformation.TryParse(transformationString, out transformation) == false)
                return false;

            string pixelRatioString;
            if (url.Query.TryGetValue("pr", out pixelRatioString))
            {
                double pixelRatio;
                if (double.TryParse(pixelRatioString, NumberStyles.Float, CultureInfo.InvariantCulture, out pixelRatio))
                {
                    pixelRatio = Math.Round(Math.Max(1.0, Math.Min(2.0, pixelRatio)), 1);

                    var responsiveTransformation = transformation as IResponsiveImageTransformation;
                    if (responsiveTransformation != null)
                    {
                        transformation = responsiveTransformation.Scale(pixelRatio);
                    }
                }
            }
            var transformedImageUrl = this.webImageTransformationService.GetTransformedImageUrl(
                url.Path.ToUrl(),
                transformation);

            context.RewritePath(transformedImageUrl.ToString());
            context.Response.Cache.SetExpires(Time.Now + TimeSpan.FromDays(365));
            return true;
        }

        private static string DecodeBreakpoints(string encodedBreakpoints)
        {
            byte[] x;
            try
            {
                x = MachineKey.Decode(encodedBreakpoints, MachineKeyProtection.Validation);
            }
            catch (ArgumentException)
            {
                //
                // MachineKey.Decode throws ArgumentException when it does not like encodedData. 
                // There is no sensible (without knowing the impl details of Decode) way we can validate the data before passing it to Decode.
                //
                return null;
            }

            if (x == null)
                return null;

            return Encoding.ASCII.GetString(x);
        }
    }
}